Skip to Content

03. Prompt Engineering 与 Context Engineering

本章高频面试题

  1. 什么是 Prompt Engineering?什么是 Context Engineering?两者有什么区别?
  2. 为什么很多 Agent 的问题不是 Prompt 写得差,而是 Context 设计得差?
  3. System Prompt、Developer Prompt、User Prompt 应该如何分层?
  4. 什么是动态 Prompt?什么时候该动态注入上下文?
  5. 为什么”把所有信息都塞给模型”通常不是最优解?长上下文为什么会退化?
  6. 长上下文下,如何避免信息污染、目标偏移和召回失真?什么是 context rot?
  7. Prompt Caching 怎么设计才能真正命中?Anthropic 和 OpenAI 的 cache 行为有什么差别?
  8. Prompt 工程的最佳实践有哪些?Context 工程的最佳实践有哪些?
  9. 结构化输出应该用 JSON mode、tool use 还是 constrained decoding?
  10. 什么应该通过 Prompt 解决,什么必须通过系统设计解决?

1. 什么是 Prompt Engineering

Prompt Engineering 是指为了让模型更稳定地产出符合要求的结果,而去设计输入指令、示例、输出约束和消息结构的过程。

简单说,它解决的是:

如何把任务讲清楚,让模型更容易正确执行。

Prompt 工程通常会涉及这些内容:

  • 角色设定
  • 任务说明
  • 约束条件
  • 输出格式
  • few-shot 示例
  • 工具调用说明

2. 什么是 Context Engineering

Context Engineering 比 Prompt 更广。

它不是只关心”写什么提示词”,而是关心:

在某一次模型调用前,究竟应该把哪些信息、以什么格式、在什么时机、放进模型上下文里。

这包括:

  • system prompt
  • 当前任务状态
  • 历史消息
  • 检索结果
  • 工具列表
  • 用户偏好
  • 权限信息
  • 环境配置
  • 输出 schema

你可以把两者理解成:

  • Prompt Engineering:如何写说明书
  • Context Engineering:如何准备整张作战桌面

3. 为什么 Agent 的核心更接近 Context Engineering

LangChain 官方文档把这点讲得非常明确:

Agent 不可靠,很多时候不是因为模型能力不够,而是因为没有把”正确的上下文”传给模型。

如果你只是问模型一个简单问题,Prompt 往往足够。 但在 Agent 场景下,模型每轮需要看到的不只是文本说明,还包括:

  • 当前目标
  • 已完成步骤
  • 可用工具
  • 上一步工具结果
  • 用户权限
  • 需要遵守的边界

所以 Agent 工程本质上非常依赖 Context Engineering。

4. Prompt 分层:System / Developer / User

4.1 System Prompt

它主要定义稳定、长期有效的高优先级规则,比如:

  • 你是谁
  • 你能做什么
  • 你不能做什么
  • 风格和安全边界

System prompt 是最适合做 prompt caching 的位置(见 §8),所以应该尽量保持稳定、避免每次请求都改。

4.2 Developer Prompt

它主要承载应用开发者定义的任务策略和格式规范,例如:

  • 回答要包含引用
  • 工具调用顺序建议
  • 输出 JSON 或 Markdown 结构
  • 特定业务规则

4.3 User Prompt

它是用户当前这一轮提出的任务目标。

最常见的错误是把所有内容都塞到 User Prompt。 更好的做法是把不同层级的约束拆开。

4.4 结构化标签:XML vs Markdown

一个容易忽略的细节:不同模型偏好的结构化标签不一样

  • Claude 系列在官方 prompting guide 中明确推荐 XML 标签,例如 <instructions>...</instructions><context>...</context><example>...</example>
  • OpenAI 系列对 Markdown 标题和列表友好
  • 开源模型通常对清晰的分节 header 敏感

如果你在多模型路由,建议统一抽象成”section → 具体渲染”的模式,由 adapter 层按目标模型转成 XML / Markdown / 其他。

5. Prompt 工程的核心实践

5.1 把目标、约束、成功标准说清楚

差的 Prompt:

“帮我分析一下这个需求。”

更好的 Prompt:

“请把这个需求拆成目标、输入输出、边界条件、风险点和待确认问题五部分。如果信息不足,请先列出缺失信息,不要直接假设实现方案。“

5.2 用结构帮助模型理解层次

# 角色 你是一个资深后端工程师。 # 目标 设计一个订单退款 Agent 的工具调用策略。 # 约束 - 不允许直接退款 - 必须先检查订单状态 - 涉及资金变更必须人工确认 # 输出格式 请输出: 1. 工具清单 2. 调用顺序 3. 风险控制点

5.3 用 few-shot 教”模式”,不用 few-shot 塞”知识”

few-shot 最适合演示:

  • 任务的模式
  • 输出的风格
  • 正确和错误样例

不要把 few-shot 当成知识库替代品。 知识应该通过检索或数据库传入,而不是靠示例硬塞。

5.4 Prompt 要版本化,且和 eval 一起迭代

Prompt 是应用逻辑的一部分。 如果不版本化,就很难知道:

  • 哪个版本效果更好
  • 什么时候改坏了
  • 模型升级后是不是 prompt 不再适配

实践上:

  • Prompt 文件放代码仓,打版本号(语义化或 hash)
  • 每次 prompt 变更必须跑回归评估集(见第 9 章)
  • Trace metadata 里带 prompt_version,线上问题能定位到版本
  • 模型升级(Opus 4.6 → 4.7)当作独立的 prompt 重评事件处理

6. TypeScript 示例:把 Prompt 模板结构化

type PromptContext = { userRole: "viewer" | "operator" | "admin"; task: string; constraints: string[]; outputFormat: "markdown" | "json"; }; export function buildDeveloperPrompt(ctx: PromptContext): string { const roleRules = ctx.userRole === "admin" ? "当前用户具备管理员权限,但高风险操作仍需确认。" : ctx.userRole === "operator" ? "当前用户具备操作权限,只允许低风险写操作。" : "当前用户为只读权限,只允许检索和分析。"; return [ "# 角色", "你是一个可靠的 Agent 执行器。", "", "# 权限", roleRules, "", "# 当前任务", ctx.task, "", "# 约束", ...ctx.constraints.map((item) => `- ${item}`), "", "# 输出要求", ctx.outputFormat === "json" ? "最终输出必须符合预定义 JSON Schema。" : "最终输出使用 Markdown,必须包含结论、依据和风险。", ].join("\n"); }

这里的重点不是”字符串拼接”本身,而是:

  • Prompt 输入被结构化了
  • 各种约束被显式建模了
  • 后续更容易做测试和版本管理

7. Context Engineering 的三层视角

LangChain 官方文档把 Agent 的 context 拆成三类,很值得记住。

7.1 Model Context

某一次模型调用时,真正喂给模型的上下文内容:

  • instructions
  • messages
  • tools
  • output format
  • 当前选择的模型

7.2 Tool Context

工具在执行时能读取和写入什么:

  • 当前 state
  • runtime context
  • store
  • 权限
  • 外部连接

7.3 Life-cycle Context

在模型调用和工具调用之间系统还会做什么:

  • 历史消息压缩
  • guardrails
  • logging
  • summarization
  • state 更新

这个分类提醒你:

Agent 可靠性不只是 prompt 内容问题,而是整个调用生命周期的问题。

8. Prompt Caching:工程上最值钱的优化之一

Prompt caching 是 2025-2026 生产 Agent 里最容易被低估的成本杠杆。以 Anthropic 为例:cache 写入是基础输入价的 1.25x(5 分钟 TTL)或 2x(1 小时 TTL),但 cache 读只要 0.1x。稳定的 system prompt + tool schema + 长参考文档前缀被反复命中时,成本能直接砍到十分之一,TTFB 也明显下降。

8.1 Anthropic 的 cache_control 语义

Anthropic 用显式 cache_control 标记缓存断点:

import Anthropic from "@anthropic-ai/sdk"; const client = new Anthropic(); const response = await client.messages.create({ model: "claude-opus-4-7", max_tokens: 1024, system: [ { type: "text", text: LARGE_SYSTEM_PROMPT, cache_control: { type: "ephemeral" }, }, ], tools: [ /* ... */ ], messages: [ { role: "user", content: "..." }, { role: "assistant", content: "...", // 在会话快照处打一个 breakpoint cache_control: { type: "ephemeral" }, }, { role: "user", content: "新的问题" }, ], }); console.log(response.usage); // cache_creation_input_tokens / cache_read_input_tokens / input_tokens

几条关键规则(对照 Anthropic 官方文档,2026-04 状态):

  • 每个请求最多 4 个 cache breakpoint
  • 默认 TTL 5 分钟,可选 ttl: "1h" 换 2x 写入成本
  • 最小缓存长度:Claude Opus 4.7/4.6/4.5 和 Haiku 4.5 是 4096 tokens,Sonnet 系列 2048,低于阈值会静默不缓存
  • Cache 读不计入 rate limit
  • cache_control 之前的所有内容都会被缓存,所以顺序是 system prompt → tools → 历史消息,越稳定的放越前面

8.2 让 cache 真正命中的工程规矩

  1. 前缀稳定:凡是放在 system prompt 和 tools 的内容,任何字段的改动(哪怕重排)都会让缓存失效
  2. 动态部分后置:用户输入、当前时间戳、workspace 特定上下文放在 messages 后段
  3. Tool schema 分离:高频迭代的 description 和稳定的 schema 分层管理,schema 变更打版本
  4. 监控命中率cache_read_input_tokens / (cache_read + cache_creation + input) 是核心 KPI,低于 50% 说明架构有问题
  5. 小心时间戳:system prompt 里写”今天是 {now}“会让前缀每秒变一次,直接毁掉缓存

OpenAI 的 prompt caching 是自动的(无需显式标记),但同样依赖前缀稳定性,规矩完全类似。

9. 为什么”把所有信息都塞给模型”通常不是最优解

很多初学者有一个直觉:

“只要给模型更多信息,它就会更准。”

这在实际中经常不成立。几个已经被反复验证的问题:

9.1 Lost in the Middle

2023 年 Stanford 等团队的研究明确指出:模型对上下文中间位置的信息召回精度显著低于头尾。这个现象在 2024-2025 的长上下文模型上仍然存在(虽然有所缓解)。

工程含义:重要信息放头放尾,不要埋在 50k tokens 中间。

9.2 Context Rot

长上下文会随对话进行逐步退化——哪怕窗口没满,模型的任务遵循能力和工具选择准确率也会下降。这在 2024-2025 多个团队的评测里被反复观察到(Anthropic、Chroma 等都发过相关报告)。

工程含义:定期 compact,不要追求”把一切塞进窗口”。

9.3 注意力稀释

历史信息和当前目标混杂会导致注意力漂移,检索召回的片段太多会让真正关键的证据不突出。

所以 Context Engineering 的关键不是”更多”,而是”更对”。

10. Context Engineering 的推荐做法

10.1 分层组织上下文

比较稳的做法是把上下文拆成几个槽位:

  1. 稳定规则(system prompt)
  2. 工具/skill 清单(catalog)
  3. 当前任务状态(显式 state)
  4. 最近对话窗口
  5. 检索结果(带引用元数据)
  6. 权限和环境

不要把这些内容混成一大段自然语言。

10.2 只给”此刻有用的上下文”

例如:

  • 当前在写邮件,就不需要把全部工具说明都给模型
  • 当前在审批退款,就应该把权限、订单状态和审批规则显式注入
  • 当前在检索问答,就应重点注入证据片段和引用格式要求

10.3 动态注入上下文

不同轮次、不同用户、不同阶段,模型看到的内容可以不同。

LangChain 用 dynamicSystemPromptMiddleware 承载这个思路:

import { z } from "zod"; import { createAgent, dynamicSystemPromptMiddleware } from "langchain"; const contextSchema = z.object({ userRole: z.enum(["viewer", "operator", "admin"]), }); type RuntimeContext = z.infer<typeof contextSchema>; const agent = createAgent({ model: "anthropic:claude-opus-4-7", tools: [], contextSchema, middleware: [ dynamicSystemPromptMiddleware<RuntimeContext>((state, runtime) => { let prompt = "你是一个可靠的企业助手。"; if (runtime.context.userRole === "viewer") { prompt += "\n当前用户只读,禁止建议执行写操作。"; } if (state.messages.length > 10) { prompt += "\n当前对话较长,请保持回答简洁,只突出关键结论。"; } return prompt; }), ], });

新版 LangChain 的 middleware 还提供 wrapModelCallwrapToolCallbeforeModelafterModel 等 hook,你可以在模型调用前做消息裁剪、权限过滤、工具可见性动态控制;在调用后做结果审查。createMiddleware({...}) 把这些拼成可组合的单位。

10.4 对长历史做压缩和选择(Compaction)

不要默认把整段历史原样传给模型。常用手段:

  • 滑动窗口:只保留最近 N 轮
  • 阶段性摘要:每 M 轮或每个 milestone 触发一次 LLM summary,把细节压缩成结构化要点
  • Semantic checkpoint:在任务关键节点(计划确认、审批通过、重大结果)生成 checkpoint summary,之后的对话基于此 checkpoint
  • Scratchpad offload:把中间推理和工具调用结果写到外部 state/文件系统,而不是全塞在 message history 里(这是 Claude Code、Deep Agents 等系统的核心模式)
  • Hybrid retrieval over history:把历史消息本身向量化,按需检索相关片段

一个实用原则:超过窗口 50% 就应该考虑 compact。等满了再做是被动的,会有 token 浪费和延迟尖峰。

11. 结构化输出:JSON mode vs Tool use vs Constrained decoding

这是一个面试常问、工程常踩坑的点。

11.1 几种方式

  1. Prompt 里要求 JSON:最不稳定,依赖模型”听话”
  2. Provider 的 JSON mode:OpenAI 的 response_format: { type: "json_object" }、Anthropic 靠 system prompt + tool use。保证”是合法 JSON”,不保证”符合某个 schema”
  3. Structured Outputs / 强 schema:OpenAI 的 response_format: { type: "json_schema", ... strict: true }、Anthropic 通过 tool use 强制 schema。这是当前生产推荐
  4. Tool use 作为伪装的结构化输出:把”输出结构化结果”当成一次 tool call,schema 是工具的 input schema
  5. Constrained decoding / grammar:推理栈支持时(vLLM、llama.cpp、Outlines),token 级强制符合 grammar,是最硬的约束

11.2 工程选择原则

  • 能用 provider 的 strict schema 就用。OpenAI 的 strict: true 和 Anthropic 的 tool use 现在都够稳
  • Tool use 是天然的结构化输出机制。如果你的 agent 反正要 tool call,最终结构化答案本身就是一次 tool call,schema 复用就行
  • Open-ended 生成配合”先结构后正文”。例如先输出 { "plan": [...], "needs_tool": true },再生成自然语言
  • 记得防御解析失败。即使是 strict,网络错误、truncation、模型降级都可能让 JSON 坏掉——必须有一层 robust 解析 + 重试

12. 什么应该靠 Prompt 解决,什么必须靠系统解决

更适合靠 Prompt 的问题

  • 输出风格
  • 结构化表达
  • 任务说明
  • 审慎回答策略
  • 轻量格式要求

更适合靠系统解决的问题

  • 权限控制(server-side 校验)
  • 工具可见性(tool filtering)
  • 上下文裁剪(middleware)
  • 长期记忆管理(存储层 + 写入策略)
  • 错误恢复(runtime)
  • 事件流(event store)
  • 结果校验(schema + validator)
  • 人工审批(interrupt)

一个很好记的原则:

Prompt 负责”表达意图”,系统负责”保证边界和稳定性”。

13. 本章方法论小结

  1. Prompt Engineering 解决”怎么把任务讲清楚”
  2. Context Engineering 解决”模型这一轮究竟该看到什么”
  3. Agent 的可靠性问题,很多时候更像 Context 问题而不是 Prompt 问题
  4. Prompt 要分层、要版本化、要和 eval 一起迭代
  5. Prompt caching 是成本杠杆:稳定前缀 + 动态后置 + 命中率监控
  6. 上下文不是越多越好,而是越对越好——警惕 lost in the middle 和 context rot
  7. 动态上下文、分层注入、压缩与检索、scratchpad offload,是生产系统的关键能力
  8. 结构化输出优先用 provider 的 strict schema 或 tool use,不要依赖”prompt 里请求 JSON”
  9. Prompt 不能替代权限、状态机、校验和恢复机制
Last updated on